/**
    @project {
        "templateId": "org.helgoboss.custom",
        "version": "0.1-SNAPSHOT",
        "dependencies": [
            "org.scala-lang:scala-library:2.9.0-1",
            "org.helgoboss:osgi-additions:0.1-SNAPSHOT",
            "org.helgoboss:scala-additions:0.1-SNAPSHOT",
            "org.helgoboss:commandlet-api:0.1-SNAPSHOT",
            "org.helgoboss:launchlet-api:0.1-SNAPSHOT",
            "org.helgoboss:commandlet-convenience:0.1-SNAPSHOT",
            "org.helgoboss:bindex:2.2.0",
            "org.helgoboss:task:0.1-SNAPSHOT"
        ]
    }
*/

package org.helgoboss.obr_filter

import org.helgoboss.osgi_additions._
import metatype.{ElementaryAttribute, ObjectClass}
import org.helgoboss.commandlet._
import org.helgoboss.launchlet._
import org.helgoboss.scala_additions.Implicits._
import org.helgoboss.scala_additions.RichFile
import org.osgi.impl.bundle.obr.resource._
import org.helgoboss.commandlet_convenience.Implicits._
import java.nio.file.{StandardCopyOption, Files}
import org.osgi.service.bindex.BundleIndexer
import java.io.{BufferedOutputStream, FileOutputStream, File}
import org.helgoboss.scala_additions.Path._
import org.helgoboss.task.ExecuteAsTask

class Activator extends OsgiContext with OsgiConfigurationWatcher with OsgiServiceWatcher with OsgiMetaTypeProvider
        with OsgiConsumer {
    whenBundleActive {
        provideMetaType(objectClass)
        whenServicePresent[BundleIndexer] { bundleIndexer =>
            whenConfigurationUpdated("org.helgoboss.obr_filter") { conf =>
                val resultConf = conf match {
                    case Some(c) => objectClass.defaultConfig ++ c
                    case None => objectClass.defaultConfig
                }
                val defaultSourceDir = resultConf.get("defaultSourceDir").map(_.asInstanceOf[String]).map(new File(_))
                val defaultTargetDir = resultConf.get("defaultTargetDir").map(_.asInstanceOf[String]).map(new File(_))
                val defaultIncludePatterns = resultConf.get("defaultIncludePatterns").map(_.asInstanceOf[String].split(',').toList)
                val commandlet = new ObrFilterCommandlet(bundleIndexer, defaultSourceDir, defaultTargetDir,
                    defaultIncludePatterns, optionalService[ExecuteAsTask])
                commandlet.providesService[Commandlet, Launchlet]
            }
        }
    }


    lazy val objectClass = ObjectClass (
        id = "org.helgoboss.obr_filter",
        name = "OBR Filter",
        requiredAttributes = List (
            ElementaryAttribute[String](id = "defaultSourceDir", name = "Default source directory", default = Some(initialDefaultSourceDir.getAbsolutePath)),
            ElementaryAttribute[String](id = "defaultTargetDir", name = "Default target directory"),
            ElementaryAttribute[String](id = "defaultIncludePatterns", name = "Default include patterns", description = "comma-separated", default = Some("**/*"))
        )
    )

    lazy val initialDefaultSourceDir = RichFile.userHomeDir / ".m2/repository"

}


class ObrFilterCommandlet(bundleIndexer: BundleIndexer,
                          defaultSourceDir: Option[File],
                          defaultTargetDir: Option[File],
                          defaultIncludePatterns: Option[Seq[String]],
                          executeAsTask: Option[ExecuteAsTask]) extends Commandlet with Launchlet {

	val id = "org.helgoboss.obr-filter"
    val name = "OBR Filter"

    def execute(context: LaunchletExecutionContext) = {
        context.outputStream match {
            case Some(os) =>
                filterObr(arguments = context.arguments, say = os.println, reportProgress = (p => ()))

            case None =>
                executeAsTask.get(name = "Filter OBR") { task =>
                    filterObr(arguments = context.arguments, say = task.logStream.println,
                        reportProgress = (p => task.progress_=(p)))
                }
        }
    }
    
	def execute(context: ExecutionContext) = {
        filterObr(arguments = context.arguments, say = context.outputStream.println, reportProgress = (p => ()))
    }

    private def filterObr(arguments: Seq[String], say: Any => Any, reportProgress: Int => Unit): Int = {
        val options = arguments.options
        val sourceDir = options.qualified.get("sourceDir").map(new File(_)) getOrElse defaultSourceDir.get
        val targetDir = options.qualified.get("targetDir").map(new File(_)) getOrElse (defaultTargetDir getOrElse
                sys.error("Target directory not specified"))
        val includePatterns = options.qualified.get("includePatterns").map(_.split(",").toList) getOrElse defaultIncludePatterns.get

        filterObr(sourceDir = sourceDir, targetDir = targetDir, includePatterns = includePatterns, say = say,
            reportProgress = reportProgress)
        0
    }

    private def filterObr(sourceDir: File, targetDir: File, includePatterns: Seq[String], say: Any => Any,
                          reportProgress: Int => Unit) {
        val repo = new RepositoryImpl(targetDir.toURI.toURL)

        def isBundle(jarFile: File) = {
            try {
                val bundleInfo = new BundleInfo(repo, jarFile)
                val resource = bundleInfo.build()
                !resource.getSymbolicName.startsWith("Untitled-")
            } catch {
                case _ => false
            }
        }

        say("Using include patterns [" + includePatterns.mkString(",") + "]")
        say("Copying bundles from [" + sourceDir + "] to [" + targetDir + "]")

        val candidateNodes = sourceDir.tree.include(includePatterns: _*).include("**/*.ar").toList
        var numFilesProcessed = 0
        def candidateProcessed {
            numFilesProcessed += 1
            val percentage = 80 * numFilesProcessed / candidateNodes.size
            reportProgress(percentage)
        }
        val bundleJarFiles = candidateNodes.flatMap { node =>
            val result: Option[File] = if (isBundle(node.file)) {
                say(node.path)
                val targetFile = targetDir / node.path
                targetFile.getParentFile.mkdirs()
                Files.copy(node.file.toPath, targetFile.toPath, StandardCopyOption.REPLACE_EXISTING)
                Some(targetFile)
            } else {
                None
            }
            candidateProcessed
            result
        }

        say("\nIndexing ...")
        val repositoryXmlFile = targetDir / "repository.xml"
        val outputStream = new BufferedOutputStream(new FileOutputStream(repositoryXmlFile))
        try {
            import collection.JavaConversions._
            bundleIndexer.index(new java.util.HashSet(bundleJarFiles), outputStream, new java.util.HashMap[String, String])
        } finally {
            outputStream.close()
        }
        reportProgress(100)
    }

}